{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# import"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-04-02T06:04:21.844393Z",
     "start_time": "2019-04-02T06:04:19.953231Z"
    }
   },
   "outputs": [],
   "source": [
    "import os\n",
    "os.environ[\"CUDA_VISIBLE_DEVICES\"] = \"0\"\n",
    "import tensorflow as tf\n",
    "import numpy as np\n",
    "import time\n",
    "import random\n",
    "import pickle\n",
    "\n",
    "tf.logging.set_verbosity(tf.logging.INFO)\n",
    "sess_conf = tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-04-02T06:04:21.883876Z",
     "start_time": "2019-04-02T06:04:21.846827Z"
    }
   },
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "import os\n",
    "\n",
    "def linear(input_, output_size, scope=None):\n",
    "    '''\n",
    "    Linear map: output[k] = sum_i(Matrix[k, i] * input_[i] ) + Bias[k]\n",
    "    Args:\n",
    "    input_: a tensor or a list of 2D, batch x n, Tensors.\n",
    "    output_size: int, second dimension of W[i].\n",
    "    scope: VariableScope for the created subgraph; defaults to \"Linear\".\n",
    "  Returns:\n",
    "    A 2D Tensor with shape [batch x output_size] equal to\n",
    "    sum_i(input_[i] * W[i]), where W[i]s are newly created matrices.\n",
    "  Raises:\n",
    "    ValueError: if some of the arguments has unspecified or wrong shape.\n",
    "  '''\n",
    "\n",
    "    shape = input_.get_shape().as_list()\n",
    "    if len(shape) != 2:\n",
    "        raise ValueError(\"Linear is expecting 2D arguments: %s\" % str(shape))\n",
    "    if not shape[1]:\n",
    "        raise ValueError(\"Linear expects shape[1] of arguments: %s\" % str(shape))\n",
    "    input_size = shape[1]\n",
    "\n",
    "    # Now the computation.\n",
    "    with tf.variable_scope(scope or \"SimpleLinear\"):\n",
    "        matrix = tf.get_variable(\"Matrix\", [output_size, input_size], dtype=input_.dtype)\n",
    "        bias_term = tf.get_variable(\"Bias\", [output_size], dtype=input_.dtype)\n",
    "\n",
    "    return tf.matmul(input_, tf.transpose(matrix)) + bias_term\n",
    "\n",
    "\n",
    "def highway(input_, size, num_layers=1, bias=-2.0, f=tf.nn.relu, scope='Highway'):\n",
    "    \"\"\"Highway Network (cf. http://arxiv.org/abs/1505.00387).\n",
    "    t = sigmoid(Wy + b)\n",
    "    z = t * g(Wy + b) + (1 - t) * y\n",
    "    where g is nonlinearity, t is transform gate, and (1 - t) is carry gate.\n",
    "    \"\"\"\n",
    "\n",
    "    with tf.variable_scope(scope):\n",
    "        for idx in range(num_layers):\n",
    "            g = f(linear(input_, size, scope='highway_lin_%d' % idx))\n",
    "\n",
    "            t = tf.sigmoid(linear(input_, size, scope='highway_gate_%d' % idx) + bias)\n",
    "\n",
    "            output = t * g + (1. - t) * input_\n",
    "            input_ = output\n",
    "\n",
    "    return output\n",
    "\n",
    "\n",
    "class TextCNN(object):\n",
    "    \"\"\"\n",
    "    A CNN for text classification.\n",
    "    Uses an embedding layer, followed by a convolutional, max-pooling and softmax layer.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(\n",
    "            self, sess, sequence_length, num_classes, vocab_size, dp,\n",
    "            emd_dim, filter_sizes, num_filters, l2_reg_lambda=0.0, dropout_keep_prob = 1):\n",
    "        # Placeholders for input, output and dropout\n",
    "        self.input_x = tf.placeholder(tf.int32, [None, sequence_length], name=\"input_x\")\n",
    "        self.input_y = tf.placeholder(tf.float32, [None, num_classes], name=\"input_y\")\n",
    "        self.dropout_rate = dropout_keep_prob\n",
    "        self.dropout_input = tf.placeholder(tf.float32, name=\"dropout_keep_prob\")\n",
    "        self.sess = sess\n",
    "        self.max_sentence_len = sequence_length\n",
    "        self.dp = dp\n",
    "        # Keeping track of l2 regularization loss (optional)\n",
    "        l2_loss = tf.constant(0.0)\n",
    "\n",
    "        with tf.variable_scope('TextCNN'):\n",
    "            # Embedding layer\n",
    "            with tf.device('/cpu:0'), tf.name_scope(\"embedding\"):\n",
    "                self.W = tf.Variable(\n",
    "                    tf.random_uniform([vocab_size, emd_dim], -1.0, 1.0),\n",
    "                    name=\"W\")\n",
    "                self.embedded_chars = tf.nn.embedding_lookup(self.W, self.input_x)\n",
    "                self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)\n",
    "\n",
    "            # Create a convolution + maxpool layer for each filter size\n",
    "            pooled_outputs = []\n",
    "            for filter_size, num_filter in zip(filter_sizes, num_filters):\n",
    "                with tf.name_scope(\"conv-maxpool-%s\" % filter_size):\n",
    "                    # Convolution Layer\n",
    "                    filter_shape = [filter_size, emd_dim, 1, num_filter]\n",
    "                    W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name=\"W\")\n",
    "                    b = tf.Variable(tf.constant(0.1, shape=[num_filter]), name=\"b\")\n",
    "                    conv = tf.nn.conv2d(\n",
    "                        self.embedded_chars_expanded,\n",
    "                        W,\n",
    "                        strides=[1, 1, 1, 1],\n",
    "                        padding=\"VALID\",\n",
    "                        name=\"conv\")\n",
    "                    # Apply nonlinearity\n",
    "                    h = tf.nn.relu(tf.nn.bias_add(conv, b), name=\"relu\")\n",
    "                    # Maxpooling over the outputs\n",
    "                    pooled = tf.nn.max_pool(\n",
    "                        h,\n",
    "                        ksize=[1, sequence_length - filter_size + 1, 1, 1],\n",
    "                        strides=[1, 1, 1, 1],\n",
    "                        padding='VALID',\n",
    "                        name=\"pool\")\n",
    "                    pooled_outputs.append(pooled)\n",
    "\n",
    "            # Combine all the pooled features\n",
    "            num_filters_total = sum(num_filters)\n",
    "            self.h_pool = tf.concat(pooled_outputs, 3)\n",
    "            self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])\n",
    "\n",
    "            # Add highway\n",
    "            with tf.name_scope(\"highway\"):\n",
    "                self.h_highway = highway(self.h_pool_flat, self.h_pool_flat.get_shape()[1], 1, 0)\n",
    "\n",
    "            # Add dropout\n",
    "            with tf.name_scope(\"dropout\"):\n",
    "                self.h_drop = tf.nn.dropout(self.h_highway, self.dropout_input)\n",
    "\n",
    "            # Final (unnormalized) scores and predictions\n",
    "            with tf.name_scope(\"output\"):\n",
    "                W = tf.Variable(tf.truncated_normal([num_filters_total, num_classes], stddev=0.1), name=\"W\")\n",
    "                b = tf.Variable(tf.constant(0.1, shape=[num_classes]), name=\"b\")\n",
    "                l2_loss += tf.nn.l2_loss(W)\n",
    "                l2_loss += tf.nn.l2_loss(b)\n",
    "                self.scores = tf.nn.xw_plus_b(self.h_drop, W, b, name=\"scores\")\n",
    "                self.ypred_for_auc = tf.nn.softmax(self.scores)\n",
    "                self.predictions = tf.argmax(self.scores, 1, name=\"predictions\")\n",
    "\n",
    "            # CalculateMean cross-entropy loss\n",
    "            with tf.name_scope(\"loss\"):\n",
    "                losses = tf.nn.softmax_cross_entropy_with_logits(logits=self.scores, labels=self.input_y)\n",
    "                self.loss = tf.reduce_mean(losses) + l2_reg_lambda * l2_loss\n",
    "                self.d_loss = tf.reshape(tf.reduce_mean(self.loss), shape=[1])\n",
    "                \n",
    "            with tf.name_scope(\"accuracy\"):\n",
    "                correct_predictions = tf.equal(self.predictions, tf.argmax(self.input_y, 1))\n",
    "                self.accuracy = tf.reduce_mean(tf.cast(correct_predictions, \"float\"), name=\"accuracy\")\n",
    "                \n",
    "        self.params = tf.trainable_variables()\n",
    "        d_optimizer = tf.train.AdamOptimizer(1e-4)\n",
    "        grads_and_vars = d_optimizer.compute_gradients(self.loss, self.params, aggregation_method=2)\n",
    "        self.train_op = d_optimizer.apply_gradients(grads_and_vars)\n",
    "        #self.saver = tf.train.Saver([v for v in tf.trainable_variables() if 'summary_' not in v.name], max_to_keep = 5)\n",
    "        self.saver = tf.train.Saver(tf.trainable_variables(), max_to_keep = 5)\n",
    "        self.summary_placeholders, self.update_ops, self.summary_op = self.setup_summary()\n",
    "        self.sess.run(tf.global_variables_initializer())\n",
    "        \n",
    "    def save(self, path, epoch):\n",
    "        checkpoint_prefix = os.path.join(path, \"model\")\n",
    "        self.saver.save(self.sess, checkpoint_prefix, global_step=epoch)\n",
    "        print('save to %s success' % checkpoint_prefix)\n",
    "        \n",
    "    def restore(self, path):\n",
    "        self.saver.restore(self.sess, path)\n",
    "        print('restore %s success' % path)\n",
    "        \n",
    "    def setup_summary(self):\n",
    "        train_loss_ = tf.Variable(0., name='summary_train_loss')\n",
    "        tf.summary.scalar('Train_loss', train_loss_)\n",
    "        train_acc_ = tf.Variable(0., name='summary_train_acc')\n",
    "        tf.summary.scalar('Train_Acc', train_acc_)\n",
    "        \n",
    "        test_loss_ = tf.Variable(0., name='summary_train_loss')\n",
    "        tf.summary.scalar('Train_loss', test_loss_)\n",
    "        test_acc_ = tf.Variable(0., name='summary_test_acc')\n",
    "        tf.summary.scalar('Test_Acc', test_acc_)\n",
    "        \n",
    "        summary_vars = [train_loss_, train_acc_, test_loss_, test_acc_]\n",
    "        summary_placeholders = [tf.placeholder(tf.float32) for _ in range(len(summary_vars))]\n",
    "        update_ops = [summary_vars[i].assign(summary_placeholders[i]) for i in range(len(summary_vars))]\n",
    "        summary_op = tf.summary.merge_all()\n",
    "        return summary_placeholders, update_ops, summary_op\n",
    "\n",
    "    def infer(self, x_str):\n",
    "        X_ind = [self.dp.w2id[w] for w in x_str.split()]\n",
    "        X_pad_ind = [X_ind + [self.dp._x_pad] * (self.dp.max_length - len(X_ind))]\n",
    "        #print(X_pad_ind)\n",
    "        predict = self.sess.run(self.predictions, \n",
    "                {self.input_x: X_pad_ind,\n",
    "                self.dropout_input: 1.0})[0]\n",
    "        return predict\n",
    "        \n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-04-02T06:11:09.820459Z",
     "start_time": "2019-04-02T06:11:09.800014Z"
    }
   },
   "outputs": [],
   "source": [
    "class TextCNN_DP:\n",
    "    def __init__(self, X_indices, C_labels, w2id, batch_size, max_length, n_epoch, split_ratio=0.1, test_data=None):\n",
    "        self.n_epoch = n_epoch\n",
    "        if test_data == None:\n",
    "            num_test = int(len(X_indices) * split_ratio)\n",
    "            r = np.random.permutation(len(X_indices))\n",
    "            X_indices = np.array(X_indices)[r].tolist()\n",
    "            C_labels = np.array(C_labels)[r].tolist()\n",
    "            self.C_train = np.array(C_labels[num_test:])\n",
    "            self.X_train = np.array(X_indices[num_test:])\n",
    "            self.C_test = np.array(C_labels[:num_test])\n",
    "            self.X_test = np.array(X_indices[:num_test])\n",
    "        else:\n",
    "            self.X_train, self.C_train, self.X_test, self.C_test = test_data\n",
    "            self.X_train = np.array(self.X_train)\n",
    "            self.C_train = np.array(self.C_train)\n",
    "            self.X_test = np.array(self.X_test)\n",
    "            self.C_test = np.array(self.C_test)\n",
    "        self.max_length = max_length\n",
    "        self.num_batch = int(len(self.X_train) / batch_size)\n",
    "        self.num_steps = self.num_batch * self.n_epoch\n",
    "        self.batch_size = BATCH_SIZE\n",
    "        self.w2id = w2id\n",
    "        self.id2w = dict(zip(w2id.values(), w2id.keys()))\n",
    "        self._x_pad = w2id['<PAD>']\n",
    "        print('Train_data: %d | Test_data: %d | Batch_size: %d | Num_batch: %d | vocab_size: %d' % (len(self.X_train), len(self.X_test), BATCH_SIZE, self.num_batch, len(self.w2id)))\n",
    "        \n",
    "    def next_batch(self, X, C):\n",
    "        r = np.random.permutation(len(X))\n",
    "        X = X[r]\n",
    "        C = C[r]\n",
    "        for i in range(0, len(X) - len(X) % self.batch_size, self.batch_size):\n",
    "            X_batch = X[i : i + self.batch_size]\n",
    "            C_batch = C[i : i + self.batch_size]\n",
    "            padded_X_batch = self.pad_sentence_batch(X_batch, self._x_pad)\n",
    "            yield (np.array(padded_X_batch),\n",
    "                   C_batch)\n",
    "    \n",
    "    def sample_test_batch(self):\n",
    "        i = random.randint(0, int(len(self.C_test) / self.batch_size)-2)\n",
    "        C = self.C_test[i*self.batch_size:(i+1)*self.batch_size]\n",
    "        padded_X_batch = self.pad_sentence_batch(self.X_test[i*self.batch_size:(i+1)*self.batch_size], self._x_pad)\n",
    "        return np.array(padded_X_batch), C\n",
    "    \n",
    "        \n",
    "    def pad_sentence_batch(self, sentence_batch, pad_int):\n",
    "        padded_seqs = []\n",
    "        seq_lens = []\n",
    "        sentence_batch = sentence_batch.tolist()\n",
    "        max_sentence_len = self.max_length\n",
    "        for sentence in sentence_batch:\n",
    "            padded_seqs.append(sentence + [pad_int] * (max_sentence_len - len(sentence)))\n",
    "            seq_lens.append(len(sentence))\n",
    "        return padded_seqs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-04-02T06:08:56.493993Z",
     "start_time": "2019-04-02T06:08:56.461144Z"
    }
   },
   "outputs": [],
   "source": [
    "class TextCNN_Util:\n",
    "    def __init__(self, dp, model, display_freq=3):\n",
    "        self.display_freq = display_freq\n",
    "        self.dp = dp\n",
    "        self.D = model\n",
    "        \n",
    "    def train(self, epoch):\n",
    "        avg_c_loss = 0.0\n",
    "        avg_acc = 0.0\n",
    "        tic = time.time()\n",
    "        X_test_batch, C_test_batch  = self.dp.sample_test_batch()\n",
    "        for local_step, (X_train_batch, C_train_batch) in enumerate(\n",
    "            self.dp.next_batch(self.dp.X_train, self.dp.C_train)):\n",
    "            #print(len(C_train_batch), len(X_train_batch))\n",
    "            acc, loss, _ = self.D.sess.run([self.D.accuracy, self.D.d_loss, self.D.train_op], \n",
    "                {self.D.input_x: X_train_batch, \n",
    "                self.D.input_y: C_train_batch, \n",
    "                self.D.dropout_input: self.D.dropout_rate})\n",
    "            avg_c_loss += loss\n",
    "            avg_acc += acc\n",
    "            if (local_step % int(self.dp.num_batch / self.display_freq)) == 0:\n",
    "                val_acc, val_c_loss = self.D.sess.run([self.D.accuracy, self.D.d_loss], \n",
    "                                            {self.D.input_x: X_test_batch, \n",
    "                                            self.D.input_y: C_test_batch, \n",
    "                                            self.D.dropout_input: self.D.dropout_rate})\n",
    "                print(\"Epoch %d/%d | Batch %d/%d | Train_loss: %.3f Acc %.3f | Test_loss: %.3f Acc %.3f | Time_cost:%.3f\" % \n",
    "                      (epoch, self.n_epoch, local_step, self.dp.num_batch, avg_c_loss / (local_step + 1), avg_acc / (local_step + 1), val_c_loss, val_acc, time.time()-tic))\n",
    "                self.cal()\n",
    "                tic = time.time()\n",
    "        return avg_c_loss / (local_step + 1), avg_acc / (local_step + 1)\n",
    "    \n",
    "    def test(self):\n",
    "        avg_c_loss = 0.0\n",
    "        avg_acc = 0.0\n",
    "        tic = time.time()\n",
    "        for local_step, (X_test_batch, C_test_batch) in enumerate(\n",
    "            self.dp.next_batch(self.dp.X_test, self.dp.C_test)):\n",
    "            acc, loss = self.D.sess.run([self.D.accuracy, self.D.d_loss], \n",
    "                {self.D.input_x: X_test_batch, \n",
    "                self.D.input_y: C_test_batch, \n",
    "                self.D.dropout_input: 1.0})\n",
    "            avg_c_loss += loss\n",
    "            avg_acc += acc\n",
    "        return avg_c_loss / (local_step + 1), avg_acc / (local_step + 1)\n",
    "    \n",
    "    def fit(self, train_dir):\n",
    "        self.n_epoch = self.dp.n_epoch\n",
    "        out_dir = train_dir\n",
    "        if not os.path.exists(out_dir):\n",
    "            os.makedirs(out_dir)\n",
    "        print(\"Writing to %s\" % out_dir)\n",
    "        checkpoint_prefix = os.path.join(out_dir, \"model\")\n",
    "        self.summary_writer = tf.summary.FileWriter(os.path.join(out_dir, 'Summary'), self.D.sess.graph)\n",
    "        for epoch in range(1, self.n_epoch+1):\n",
    "            tic = time.time()\n",
    "            train_c_loss, train_acc = self.train(epoch)\n",
    "            test_c_loss, test_acc = self.test()\n",
    "            print(\"Epoch %d/%d | Train_loss: %.3f Acc %.3f | Test_loss: %.3f Acc %.3f\" % \n",
    "                  (epoch, self.n_epoch, train_c_loss, train_acc, test_c_loss, test_acc))\n",
    "            path = self.D.saver.save(self.D.sess, checkpoint_prefix, global_step=epoch)\n",
    "            print(\"Saved model checkpoint to %s\" % path)\n",
    "    \n",
    "    def show(self, sent, id2w):\n",
    "        return \" \".join([id2w.get(idx, u'&') for idx in sent])\n",
    "    \n",
    "    def cal(self, n_example=5):\n",
    "        train_n_example = int(n_example / 2)\n",
    "        test_n_example = n_example - train_n_example\n",
    "        for _ in random.sample([t for t in range(len(self.dp.X_test))], test_n_example):\n",
    "            example = self.show(self.dp.X_test[_], self.dp.id2w)\n",
    "            o = self.D.infer(example)\n",
    "            print('Test Input: %s | Output: %d | GroundTruth: %d' % (example, o, np.argmax(self.dp.C_test[_])))\n",
    "        for _ in random.sample([t for t in range(len(self.dp.X_train))], train_n_example):\n",
    "            example = self.show(self.dp.X_train[_], self.dp.id2w)\n",
    "            o = self.D.infer(example)\n",
    "            print('Train Input: %s | Output: %d | GroundTruth: %d' % (example, o, np.argmax(self.dp.C_train[_]))) \n",
    "        print(\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-04-02T06:08:59.771102Z",
     "start_time": "2019-04-02T06:08:57.637108Z"
    }
   },
   "outputs": [],
   "source": [
    "import pickle\n",
    "w2id, id2w = pickle.load(open('/workspace/Data/yelp/w2id_id2w.pkl','rb'))\n",
    "Y_train, C_train = pickle.load(open('/workspace/Data/yelp/XC_train.pkl','rb'))\n",
    "Y_dev, C_dev = pickle.load(open('/workspace/Data/yelp/XC_dev.pkl','rb'))\n",
    "Y_test, C_test = pickle.load(open('/workspace/Data/yelp/XC_test.pkl','rb'))\n",
    "print(C_train[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-04-02T06:09:00.628403Z",
     "start_time": "2019-04-02T06:08:59.774901Z"
    }
   },
   "outputs": [],
   "source": [
    "X_train = [x[:-1] for x in Y_train]\n",
    "X_dev = [x[:-1] for x in Y_dev]\n",
    "X_test = [x[:-1] for x in Y_test]\n",
    "#print(idx2str(Y_test[0]), idx2str(X_test[0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-04-02T06:11:13.826887Z",
     "start_time": "2019-04-02T06:11:12.278451Z"
    }
   },
   "outputs": [],
   "source": [
    "BATCH_SIZE = 256\n",
    "NUM_EPOCH = 30\n",
    "train_dir ='Model/YELP/TextCNN/'\n",
    "MAX_LENGTH = 16\n",
    "\n",
    "\n",
    "dp = TextCNN_DP(None, None, w2id,  BATCH_SIZE, max_length = MAX_LENGTH, n_epoch=NUM_EPOCH, test_data=(X_train, C_train, X_dev, C_dev))\n",
    "\n",
    "emb_dim = 128\n",
    "filter_sizes = [1, 2, 3, 4, 5]\n",
    "num_filters = [128, 128, 128, 128, 128]\n",
    "\n",
    "g2 = tf.Graph()\n",
    "sess2 = tf.Session(graph=g2, config=sess_conf) \n",
    "with sess2.as_default():\n",
    "    with sess2.graph.as_default():\n",
    "        D = TextCNN(sess = sess2, dp = dp, sequence_length=MAX_LENGTH, num_classes=2, vocab_size=len(dp.id2w),\n",
    "                          emd_dim = emb_dim, filter_sizes = filter_sizes, num_filters=num_filters,\n",
    "                          l2_reg_lambda=0.2, dropout_keep_prob=0.75)\n",
    "        D.sess.run(tf.global_variables_initializer())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# TrainD"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-04-02T06:28:17.441628Z",
     "start_time": "2019-04-02T06:11:13.830141Z"
    },
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "util = TextCNN_Util(dp=dp, model=D)\n",
    "util.fit(train_dir=train_dir)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "165px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
